October 2024

After I had a basis for my engine, I decided that having hardcoded meshes with random locations for objects was not good enough for the future. It was necessary to eventually have a way to import meshes and also have a way to save a scene in data. To have a proper engine, I needed the following (short-term):

Handling guizmos is not an easy task, and I did not have time either for having menus and windows add assets to a scene, especially since they would depend on guizmos afterwards. I focused mostly on the importer, and then on the scene description.

Importing geometries

Importing geometries (and animation) is a must to be able to work with an engine. I did not know many formats. The ones I knew were FBX and Alembic. The thing is that I was using libraries to access them in the past (at work), but now I do not want to depend on libraries. The choices I had were:

I was quick to dismiss USD, developped by Pixar, since it became apparent that the format did not handle animations, and I did not want to be stuck with two different file formats for animation and meshes. To my understanding, the same was true for OBJ files.

Since I dabbled with Alembic and FBX, I knew they both could do what I wanted. In my past experience, I knew that Alembic was a tighter and faster format. Also, it is from Sony, for which I work, so why not? I was able to find libraries for it, but oh boy was it big, cumbersome and ugly... Sadly, the format is not explained in detail, and reading the library's code is so obfuscated, that I felt it was easier to reverse engineer it. I found a very interesting blog on reverse engineering the Alembic format. See : Reverse engineering alembic.

By the way, the guy that wrote the blog, Ginger Bill, is the creator of the Odin programming language, which is supposed to change the video game programming world, at least for indy developers not wanting to rely on commercial engines. It is performant, and has batteries included is the sense that it already wraps many libraries (like Vulkan). See : Odin programming language.

So I started to implement my code, in C#. For simplicity, since C# is easy, I decided the Editor would handle it and make my life easier. I made the resulting binary in my own format before the engine would read it. The Alembic format is very hard, and I got as far as reading all the nodes of the hierarchy, by using an Hex Editor (Hex Editor Neo). Sadly, Ginger Bill never finished explaining the format, so after looking at the binary data for hours and trying to make sense of it, I felt it was too much effort, so I gave a chance to the FBX format instead, to see if I could get result faster.

I was extremely lucky, and somebody wrote a page on it: FBX Binary File Format. It cover the basics of the format, but not what information is where, so you still need to read many strings and hierarchies to find the information you want. My resulting code is far from perfect in the sense that my code is still very hacky, and there are a bunch of bytes I don't know the use... (not even counting all the nodes I don't know the use for either.) I will have to understand what those bytes mean some day! The format is clearly sloppier (by far) than Alembic, but it works, so, well, I'll use it.

As soon as I could get the vertex positions of the mesh, I trashed what I had for Alembic, knowing already I could make it. The buffers in FBX are compressed, but I was very lucky that the compression algorithm exists natively in C# (and the decompression obviously), so I could very easily extract buffer information. As far as I understood, the compression algorithm is : Compression algorithm , but I did not feel the courage to program a full decompression algorithm just to test if my FBX decoding worked. Maybe I got lazy, but the C# algorithm is still in use unchanged. Let's say it's acceptable.

After I could read the triangles and vertices, I could get some meshes. By the way, the assets I used, I found for free on CGT Trader , and I could edit it using Blender.

Couch first result

The couches are still randomly generated everywhere and I will come back to how I have scenes later on. It was still a good test to see if my runtime code was too slow handling meshes (it is not).

Many couches

I then managed to retrieve some UV information and apply an ugly texture I found. I just tried a bunch until it did not look too bad. It was a couch with only one UV channel as far as I could tell. Notice how much aliasing there is!

Ugly couch

I then managed to work with a model having multiple UV channels. I barely was able to explore the data in the FBX file until I could understand how it referenced the texture and how the mapping is done between the UVs and the texture. I had a white couch, but there were huge issues with the seams, where the mesh closes on itself.

Adding texture

The next step was to reference properly all the UVs and the texture. If I remember correctly, the seams issue was caused by the way I blended the texture data, and because I did not use the different UV channels properly, both things I could fix. On the next picture, we can see a wooden foot that is quite clean looking. Note that there was only one texture on that couch.

Proper texture support

I then explored in the FBX how to extract data for multiple textures. The following horse example has two different texture. The following image shows the two texture support, one is the horse, and the other is the eyes only. For the longest time, the eyes location did not align with the horse's face. It was because of where the scale is defined in the FBX and I did not take it correctly into account.

Multiple texture support

The horse has no mane in the previous picture because it is a single color and I did not know how the color was referenced in FBX. I am pretty sure that it would not be exactly the same for another model, but I will make the code more and more robust the more cases I get.

Multiple textures + 1 single color section

Having a workflow

At this point, I grew very tired of having horses at random positions and rotations in my scene, so I knew the next step was to have a file format for the scenes. I already developped a binary format for the mesh (.mesh extension).

Mesh import

The first thing I did was to expose the mesh import functionality to my editor. My editor is incredibly bad, but I wanted to make it a bit more useful. The next picture is my editor's main window (almost only window).

SolidSnow Editor's main window

Let's be honest, this is just my C++ engine embedded in a C# window with buttons on the left. Most buttons change things in the game, mostly the spatial partition strategy (can be changed live), and a camera switch. I'll come back on the Start and Restart buttons, since the interesting button now is the last one, the Mesh Import one. If I click on it, it opens a very small window with a single button (I know...), and when pressed it lets me choose a FBX file, for instance horse_hair.fbx.

Mesh import window

Then, it asks where to save the new binary file generated for it (.mesh extension). And, well, that's it, the FBX file is read, a custom binary is generated and saved in a file. I won't go over my custom format since it's basically just serializing a mesh with almost no metadata. The client sure better knows how to read it !

Saving the binary file

Scene descriptions

I wanted a way to tell my engine were to put what asset in the world, and what is better than XML for the task? Binary! Yeah, binary is great, but Unreal taught us all that binary data is impossible to merge and it is almost impossible to edit using an external tool. Let's say you want to remove an option, just write a script going through all XML and fix it. If you have binary, you need the engine to read it, edit it and save it. So I decided XML was the best, but I would also have a binary format as a shipping format.

I wrote a first small mock-up for a scene description using XML. Note that in the mock-up I reference the binary mesh files that were created. The paths are relative to the project path, which is specified in the engine INI file.

XML for a demo scene

I needed a XML parser now. That's why I have runtime tools in a static library added to the executable (in Zig baby). Now, when clicking the Start button, it opens the file explorer to select a scene, in XML. It sends it to the Zig parser that I wrote, and generates a binary scene, that is then consumed by the C++ engine. The C++ engine has no knowledge of the XML file, and the editor has no knowledge of the binary format. Zig is the glue in between them, but could be stripped off in final builds.

Opening a XML scene

Once the scene is converted to binary and consumed by the engine, it is displayed normally in the editor. For now, I have no way to modify the XML data from the editor sadly.

Result of opening the scene

Once the binary version of the scene has been generated, the engine is able to open it directly. It is also able to open the binary mesh file normally, and the mesh files reference internally pictures (bitmaps), which are read live in C by the engine. I do not have a custom binary format for pictures though. So scene is parsed, then each mesh is too, and each picture is converted into a texture, all runtime. It might get too slow, and I might need to cache some things, but for now it takes like a few milliseconds, so it's all good.

Binary scene given as an input to the standalone engine

Note that the editor can load a binary scene too.

Opening a binary scene

Adding some lighting

Around that time, I got annoyed by the fact I wanted to learn a bit of rendering, but in fact did very little outside the code I wrote to handle the different textures and shaders.

My engine compiles the shaders live. So when a demo is shipped, it comes with the shaders uncompiled. It is cool to be able to edit the shaders without changing the binaries. In the future, it might be one more thing to optimize and save under binary form in some way.

But for the moment, I was thinking more about basic lighting. The following picture is the reference for unlit horses, floor and walls. It looks like ambiant lighting, and it is, if we consider a 100% lighting on everything is ambiant lighting. For the next pictures, I set the ambiant lighting to 20% (it could be bigger) so there is always some background lighting on everything.

No lighting

The biggest effect of lighting (in a basic model) is the diffuse lighting. It is lighting that is independant of the camera position and will make objects facing lights lit, and what is behind unlit. At first I had huge issues with the meshes I was working with since the normals were all messed up. I needed to recompute the normals based on the winding of the triangles. It helped a lot to compute the normals myself.

Diffuse lighting on texture

On the single color, we could not see a single detail of the mane since there was no lighting. It looked quite ugly and popped out in the previous picture. By adding diffuse lighting on the single color shader, the details in the shape do come out nicely. Sadly, the front of the mane on top of the horse's head is all black since the winding of the mesh is not consistent and the normals I computed are pointing inwards. For now I would blame this on the model I got, but maybe there is a way for me to fix it in Blender, find proper normals or winding information in the FBX, or fix it at runtime with some rules to find the outwards normal.

Diffuse lighting on single color

The second effect I added is the specular lighting, where a spot of bright light is reflected from shiny or metallic objects. The reflectiveness of the material is a constant per material and are defined for the horse mesh in the FBX: the fur has a low value and the eyes and mane (why?) have high values. The fun thing about specular lighting is that it also depends on the position of the camera, so by just moving the camera we can see it moving. This is why the next picture is a video where we can see the specular lighting move on the horse's side.

Adding specular lighting on textures

I then added specular lighting on the mane too, and it makes it looks like plastic. Oh well... I'll respect the artist's design and leave it like this. It still looks cool though and would look good for a toy horse.

Adding specular lighting on single color

The technique I used is called a Phong reflection model and has been used for a very long time in gaming, see Phong model. I am about 25 years late to the show (if not more), but I need to have somewhere to start!